package tcln.gxtx.client.widget.grid.renderer.html;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import tcln.gxtx.client.utils.RendererPredicate;
import tcln.gxtx.client.utils.assertion.GwtAssert;
import tcln.gxtx.client.widget.treegrid.AbstractTreeGrid;
import tcln.gxtx.client.widget.treegrid.model.AbstractTreeModel;

import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.ColumnData;
import com.extjs.gxt.ui.client.widget.grid.Grid;

/**
 * A special {@link HtmlGridCellRenderer} which acts like a dispatcher to correct inner {@link HtmlGridCellRenderer}
 * according to predicates. This makes rendering data for grids is dynamic and flexible.
 * <p>
 * Initially on constructing renderers for columns in a grid/treegrid, use
 * {@link #addRenderer(HtmlGridCellRenderer, RendererPredicate)} to add an inner renderer with a predicate to determine
 * <b>when</b> this renderer should be used. Method {@link #addDefaultRenderer(HtmlGridCellRenderer)} could be used to
 * add a default renderer. The default renderer is always the last one in the search list.
 * 
 * @param <M>
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
public class ContextualHtmlGridCellRenderer<M extends AbstractTreeModel> implements HtmlGridCellRenderer<M> {
    private static final RendererPredicate DEFAULT_PREDICATE = new AlwaysTruePredicate();

    private ColumnConfig ownerColumn;
    private AbstractTreeGrid<M> ownerTree;
    private Map<RendererPredicate, HtmlGridCellRenderer<M>> innerRendererMap;

    /**
     * @param ownerColumn
     */
    public ContextualHtmlGridCellRenderer(ColumnConfig ownerColumn) {
        this();
        this.ownerColumn = ownerColumn;
    }

    /**
     * @param ownerColumn
     */
    public ContextualHtmlGridCellRenderer() {
        innerRendererMap = new LinkedHashMap<RendererPredicate, HtmlGridCellRenderer<M>>();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String render(M model, String property, ColumnData config, int rowIndex, int colIndex, ListStore<M> store,
            Grid<M> grid) {
        HtmlGridCellRenderer<M> renderer = getRenderer(model, property, rowIndex, colIndex, store, grid);
        return renderer.render(model, property, config, rowIndex, colIndex, store, grid);
    }

    /**
     * {@inheritDoc}
     */
    public void setOwnerTree(AbstractTreeGrid<M> ownerTree) {
        this.ownerTree = ownerTree;
        for (Entry<RendererPredicate, HtmlGridCellRenderer<M>> entry : innerRendererMap.entrySet()) {
            entry.getValue().setOwnerTree(ownerTree);
        }
    }

    /**
     * Returns current value of ownerColumn.
     * 
     * @return the ownerColumn.
     */
    public ColumnConfig getOwnerColumn() {
        return ownerColumn;
    }

    /**
     * Returns current value of ownerTree.
     * 
     * @return the ownerTree.
     */
    public AbstractTreeGrid<M> getOwnerTree() {
        return ownerTree;
    }

    /**
     * Adds a renderer to current group with a predicate for renderer identification.
     * <p>
     * <b>Caution</b>: The order of this method's calls reflects the order of searching for correct renderer via
     * {@link RendererPredicate}.
     * 
     * @param renderer
     *            Custom renderer. If this renderer is not specified (i.e. <code>null</code>), an empty widget is used
     *            for rendering.
     * @param predicate
     *            Predicate for renderer selection, required.
     */
    public final void addRenderer(HtmlGridCellRenderer<M> renderer, RendererPredicate predicate) {
        GwtAssert.ARGUMENTS.notNull(predicate, "Sample model data must NOT be null");
        if (renderer != null) {
            innerRendererMap.put(predicate, renderer);
        }
    }

    /**
     * Adds the default renderer to current group.
     * <p>
     * <b>Caution</b>: Because the order of this method's calls reflects the order of searching for correct renderer via
     * {@link RendererPredicate}, it's highly recommended that this method be the last renderer. <i>Putting this default
     * renderer at first position causes this renderer always be the chosen one</i>.
     * 
     * @param renderer
     *            Custom renderer. If this renderer is not specified (i.e. <code>null</code>), an empty widget is used
     *            for rendering.
     */
    public final void addDefaultRenderer(HtmlGridCellRenderer<M> renderer) {
        addRenderer(renderer, DEFAULT_PREDICATE);
    }

    private HtmlGridCellRenderer<M> getRenderer(M model, String property, int rowIndex, int colIndex,
            ListStore<M> store, Grid<M> grid) {
        for (Entry<RendererPredicate, HtmlGridCellRenderer<M>> entry : innerRendererMap.entrySet()) {
            if (entry.getKey().evaluate(model, property, rowIndex, colIndex, store, grid)) {
                return entry.getValue();
            }
        }
        return null;
    }

    private static class AlwaysTruePredicate implements RendererPredicate {
        @Override
        public boolean evaluate(ModelData model, String property, int rowIndex, int colIndex, ListStore<?> store,
                Grid<?> grid) {
            return true;
        }

    }
}
